Skip to content

feat: implement pausable streams in SuperGoodDollar contract - #303

Merged
sirpy merged 21 commits into
masterfrom
fix/supergooddollar-pause-streams
Oct 15, 2026
Merged

sirpy merged 21 commits into
masterfrom
fix/supergooddollar-pause-streams

Conversation

@blueogin

@blueogin blueogin commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator

Description

pause() did not stop streaming. The guard sat on createAgreement, which the CFA never calls when a flow is opened — it writes all flow state through updateAgreementData. Anyone could open, increase, or keep running a G$ stream while the token was paused.

This moves the guard to the path the CFA actually uses, and gates it so that opening or increasing a flow is blocked while paused, but closing, decreasing and liquidating stay available.

The bug

Verified against the installed @superfluid-finance/ethereum-contracts@1.8.1. ConstantFlowAgreementV1 calls, on the token:

  • updateAgreementData — for create, update and delete (line 1402, plus the ACL paths at 862/1079)
  • updateAgreementStateSlot — account flow state (line 1104)
  • settleBalance — line 1097

It never calls createAgreement, and never calls terminateAgreement. A flow is deleted by writing the same agreement data back with flowRate = 0.

So the existing _onlyNotPaused() on createAgreement protected nothing on the streaming path. (It is not dead code — the IDA calls createAgreement for index and subscription creation — so the guard is kept, with a corrected comment.)

The fix

Because create and delete share one code path, a blanket pause on updateAgreementData would also block shutting malicious streams down — the opposite of what is needed during an incident. The guard therefore compares the new flow rate against the stored one and rejects only an increase:

action new vs old rate while paused
open flow +n > 0 blocked
increase flow +2n > +n blocked
decrease flow +n/2 < +n allowed
close flow 0 < +n allowed
liquidation (deleteFlow) 0 < +n allowed

signextend(11, ...) sign-extends from bit 95, discarding the timestamp packed at bits 224+ and recovering the int96 flow rate. An unset slot reads 0, so a fresh flow is +n > 0 and is blocked.

About # (link your issue here)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes.

Checklist:

  • PR title matches follow: (Feature|Bug|Chore) Task Name
  • My code follows the style guidelines of this project
  • I have followed all the instructions described in the initial task (check Definitions of Done)
  • I have performed a self-review of my own code
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have added reference to a related issue in the repository
  • I have added a detailed description of the changes proposed in the pull request. I am as descriptive as possible, assisting reviewers as much as possible.
  • I have added screenshots related to my pull request (for frontend tasks)
  • I have pasted a gif showing the feature.
  • @mentions of the person or team responsible for reviewing proposed changes

Summary by Sourcery

Enforce pause and blocklist policies across Superfluid agreement operations while preserving the ability to reduce, close, and liquidate existing streams.

New Features:

  • Add pause-aware Superfluid agreement guards that prevent opening or increasing CFA and GDA outflows and block IDA value movements while preserving shutdown operations.
  • Add agreement-layer blocklist enforcement for outgoing flows, GDA distributions, and IDA payouts.
  • Add an admin burn path that bypasses ERC777 sender hooks while remaining usable during pauses and against blocked accounts.

Bug Fixes:

  • Correct stream pause enforcement by guarding the agreement callbacks actually used by the CFA instead of relying only on createAgreement.

Enhancements:

  • Extract pause and blocklist agreement logic into a linked SuperGoodDollarFlows library and expose the required Superfluid token hooks for guarded overrides.
  • Add a supply-fix implementation and deployment support for linked flow-guard libraries.
  • Improve fork-based and existing tests for stream, pool, distribution, liquidation, blocklist, and admin-burn behavior.

Build:

  • Update contract verification configuration and deployment tooling for linked libraries and additional networks.
  • Adjust dependency locking and test deployment helpers for library-linked SuperGoodDollar implementations.

Deployment:

  • Deploy and verify the SuperGoodDollarFlows library alongside SuperGoodDollar implementations.

Tests:

  • Add comprehensive unit and Celo-fork coverage for paused and blocked CFA, GDA, and IDA operations, including allowed decreases, closures, and liquidations.
  • Add regression coverage ensuring admin burns cannot be prevented by reverting ERC777 hooks.
  • Stabilize fork-based swap and timing tests against live network conditions.

Chores:

  • Remove obsolete NFT proxy setter handling and disable unused Celo gas-token tests and code paths.

Copilot AI lite review requested due to automatic review settings September 8, 2026 16:29

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="contracts/token/superfluid/SuperGoodDollar.sol" line_range="132" />
<code_context>
+			// the CFA packs int96 flowRate at bits [128,224) of the first word
+			bool increased;
+			assembly {
+				let newRate := signextend(11, shr(128, calldataload(data.offset)))
+				let oldRate := signextend(11, shr(128, sload(slot)))
+				increased := sgt(newRate, oldRate)
+			}
+			if (increased) revert SUPER_GOODDOLLAR_PAUSED();
</code_context>
<issue_to_address>
**issue (bug_risk):** `oldRate` is loaded from `sload(slot)`, but `FixedSizeData.storeData` stores the array length at `slot` and stores the first agreement-data word at `slot + 1`. For an existing CFA flow, `oldRate` is therefore the data length (normally `2`), so decreasing a normal positive flow to another positive rate is treated as an increase and reverts while paused.

**Triggers:** When an existing flow is decreased, rather than closed, while the token is paused.

**Suggested fix:** Load the first stored agreement-data word with `sload(add(slot, 1))` before extracting the packed flow rate.

```suggestion
				let oldRate := signextend(11, shr(128, sload(add(slot, 1))))
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and while paused, this changes whether Superfluid can create or increase token streams, and a faulty calldata/storage decoding or agreement-slot assumption could either leave transfers running during an incident or block needed flows. Reverting restores the gate, but any tokens transferred by an incorrectly permitted stream may not be recoverable.

Blocking findings: contracts/token/superfluid/SuperGoodDollar.sol:132


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread contracts/token/superfluid/SuperGoodDollar.sol Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new pause guard decodes newRate incorrectly because it reads the calldata array length instead of data[0], so it won’t reliably block opening/increasing streams.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes SuperGoodDollar’s pause enforcement for Superfluid Constant Flow Agreements (CFA) by moving the pause guard onto the updateAgreementData path that CFA actually uses, while preserving the ability to decrease/close/liquidate existing streams during an incident.

Changes:

  • Add a SuperfluidToken hook (_beforeAgreementDataUpdate) invoked from updateAgreementData before writing agreement data.
  • Implement pause-aware CFA flow-rate gating in SuperGoodDollar by comparing the newly written flow rate vs the stored one and rejecting only increases while paused.
  • Add tests to ensure new streams and stream increases revert while paused, but closing streams remains possible.
File summaries
File Description
test/token/SuperGoodDollar.test.ts Adds regression tests for pausing behavior across create/update/delete flow operations.
contracts/token/superfluid/SuperGoodDollar.sol Implements the pause guard on agreement-data updates by decoding and comparing flow rates.
contracts/token/superfluid/SuperfluidToken.sol Introduces a reusable pre-write hook for updateAgreementData to enable token-specific gating.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread contracts/token/superfluid/SuperGoodDollar.sol Outdated
@blueogin
blueogin requested a review from sirpy September 9, 2026 18:17

@sirpy sirpy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the DAO also have permissions to delete a flow?

Comment thread contracts/token/superfluid/SuperGoodDollar.sol Outdated
@blueogin

Copy link
Copy Markdown
Collaborator Author

@sirpy
this pr is not related to permission.
DAO can pause/unpause token, can not delete flow directly

Comment thread contracts/token/superfluid/SuperfluidToken.sol
@blueogin
blueogin requested a review from hellwolf September 15, 2026 16:45

@hellwolf hellwolf left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Looks good to me now.

I'd recommend keep tracking the upstream code when possible.

Comment thread contracts/token/superfluid/SuperGoodDollar.sol Outdated
@blueogin
blueogin requested a review from sirpy September 16, 2026 11:22
@sirpy

sirpy commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

@blueogin @hellwolf what upstream changes are we talking about? what would be the best way to keep with upstream? ie which contracts? and what method ? ie updating the ISupertoken interface and adapting to it?

@sirpy

sirpy commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

@blueogin can we move the new flow rate helper methods into an external library to reduce the size?

@blueogin

Copy link
Copy Markdown
Collaborator Author

@sirpy
upstream code is the contracts we copied from superfluid and edited - like SuperfluidToken.sol, SuperToken.sol
I think we can add a CI flow which tracks diff between forked files and upstream code

@blueogin

Copy link
Copy Markdown
Collaborator Author

@sirpy
I moved the paused-stream guard out of SuperGoodDollar into a linked library, so the token fits under the 24 KB limit.
I also updated every deploy to link that library.

@sirpy sirpy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit the library and make sure tests are passing

Comment thread contracts/token/superfluid/SuperGoodDollar.sol Outdated
Comment thread contracts/token/superfluid/SuperGoodDollar.sol Outdated
@blueogin

Copy link
Copy Markdown
Collaborator Author

@sirpy
The changes looks good
I think we can merge the pr

@sirpy
sirpy self-requested a review October 15, 2026 06:37
@sirpy
sirpy merged commit 8c7c8e0 into master Oct 15, 2026
10 checks passed
@sirpy
sirpy deleted the fix/supergooddollar-pause-streams branch October 15, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants